package net.mycrub.bitwise.core.impl;

import java.util.LinkedList;
import java.util.List;

import net.mycrub.bitwise.core.ILayer;
import net.mycrub.bitwise.core.IRetroAction;
import net.mycrub.bitwise.core.IRetroActionCallback;
import net.mycrub.bitwise.core.RetroActionType;

import org.simpleframework.xml.ElementList;
import org.simpleframework.xml.Root;

@Root
public abstract class AbstractCollaborativeLayer extends AbstractLayer {

    @ElementList
    protected LinkedList<ILayer> layers = new LinkedList<ILayer>();

    /**
     * Add a collaborating layer
     */
    public void addLayer(ILayer layer) {
        layers.add(layer);
    }

    /** {@inheritDoc} */
    public double[] process(double[] input, IRetroActionCallback callback) {
        CollabRetroActionCallback innerCallback = null;

        // If a callback has been set, then prepare to forward each layer its
        // own callback
        if (callback != null) {
            innerCallback = new CollabRetroActionCallback();
        }

        double[] result = collaborate(input, innerCallback);

        // If applicable, forward each layer its own callback
        if (callback != null && innerCallback != null) {
            callback.setRetroAction(innerCallback);
        }

        return result;
    }

    protected abstract double[] collaborate(double[] input,
            IRetroActionCallback callback);

    /** {@inheritDoc} */
    public ILayer combine(ILayer partner) {
        if (!AbstractCollaborativeLayer.class.isAssignableFrom(partner
                .getClass())) {
            throw new IllegalArgumentException(
                    "Layers are not compatible for combination "
                            + this.getClass().getName() + "/"
                            + partner.getClass().getName());
        }

        AbstractCollaborativeLayer combination = getNewInstance();
        AbstractCollaborativeLayer pplPartner = (AbstractCollaborativeLayer) partner;

        if (layers.size() != pplPartner.layers.size()) {
            throw new IllegalArgumentException("Layers are not the same size");
        }

        for (int i = 0; i < layers.size(); i++) {
            combination.addLayer(layers.get(i)
                    .combine(pplPartner.layers.get(i)));
        }

        return combination;
    }

    /** {@inheritDoc} */
    public void mutate() {
        for (ILayer layer : layers) {
            layer.mutate();
        }
    }

    /** {@inheritDoc} */
    public ILayer duplicate() {
        AbstractCollaborativeLayer clone = getNewInstance();
        for (ILayer layer : layers) {
            clone.addLayer(layer.duplicate());
        }
        return clone;
    }

    protected abstract AbstractCollaborativeLayer getNewInstance();

    /**
     * RetroAction and Callback implementation
     */
    private static class CollabRetroActionCallback implements
            IRetroActionCallback, IRetroAction {
        private List<IRetroAction> retroActions = new LinkedList<IRetroAction>();

        public void setRetroAction(IRetroAction retroAction) {
            if (retroAction != null) {
                retroActions.add(retroAction);
            }
        }

        public void process(RetroActionType type) {
            for (IRetroAction retroAction : retroActions) {
                if (retroAction != null) {
                    retroAction.process(type);
                }
            }
        }
    }
}
